/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package motorprediccion;
import motorprediccion.excepciones.datosInsuficientes;
import com.bbn.openmap.LatLonPoint;
import java.util.ArrayList;
import java.util.GregorianCalendar;

/**
 *
 * @author dani
 */
public class Colectivo {
    private int idColectivo, penalidades;
    private Recorrido recorrido;
    private DatoPosicional datoActual, datoPrevio;
    private Parada paradaActual, paradaPrevia;
    private boolean predecible;
    private GregorianCalendar horaArriboParadaPrevia, horaArriboParadaActual;
    private ArrayList<DatoPosicional> ultimosDatos;

    public GregorianCalendar getHoraArriboParadaActual() {
        return horaArriboParadaActual;
    }

    public Colectivo(int idColectivo, Recorrido recorrido) {
        this.idColectivo = idColectivo;
        this.recorrido = recorrido;
        this.datoActual = null;
        this.datoPrevio = null;
        this.paradaActual = null;
        this.paradaPrevia = null;
        this.predecible = false;
        this.penalidades = 0;
        this.horaArriboParadaActual = new GregorianCalendar();
        this.horaArriboParadaPrevia = new GregorianCalendar();
        this.ultimosDatos = new ArrayList<DatoPosicional>(0);
    }

    private void limpiarDatos() {
        this.datoActual = null;
        this.datoPrevio = null;
        this.paradaActual = null;
        this.paradaPrevia = null;
        this.predecible = false;
        this.penalidades = 0;

//        this.horaArriboParadaActual = null;
//        this.horaArriboParadaPrevia = null;
        this.ultimosDatos.clear();
    }

    public int getIdColectivo() {
        return idColectivo;
    }

    public int getRecorrido() {
        return recorrido.getId();
    }

    public ArrayList<DatoProcesado> procesarNuevoDato(DatoPosicional nuevoDato) {
        float toleranciaDiferenciaRumbo = 30; //tolerancia en grados

        //if(this.datoActual != null)
        if(ultimosDatos.size() > 0) {
            long diferenciaTiempo = nuevoDato.getFecha().getTimeInMillis() -
                    ultimosDatos.get(ultimosDatos.size()-1).getFecha().getTimeInMillis();

            diferenciaTiempo = diferenciaTiempo / 1000; //lo conviert a segundos
            int toleranciaSeg = 3*60; 
            if(diferenciaTiempo > toleranciaSeg) {
                limpiarDatos();//2min tolerancia
                //this.datoActual = nuevoDato;
            }
            //ultimosDatos.add(nuevoDato);
        }
        ultimosDatos.add(nuevoDato);

        //if(this.datoActual == null)
//        if(this.ultimosDatos.isEmpty() && paradaActual == null)
//        {
//            //this.datoActual = nuevoDato;
//            //this.datoPrevio = null;
//
//            ultimosDatos.add(nuevoDato);
//        }
//        else
        if(ultimosDatos.size()>1) {
            int paradaActualTemp;
            if(paradaActual != null)
                paradaActualTemp = this.paradaActual.getId();
            else
                paradaActualTemp = -1;            
            
            //double distanciaNuevoDato = datoActual.calcularDistancia(nuevoDato.getPosicion());
            double distanciaNuevoDato = ultimosDatos.get(ultimosDatos.size()-1).
                    calcularDistancia(ultimosDatos.get(ultimosDatos.size()-2).getPosicion());
            if(distanciaNuevoDato > 25) {//si hay suficiente destancia entre el dato nuevo y el actual, lo proceso
//                this.datoPrevio = this.datoActual;
//                this.datoActual = nuevoDato;

                if(this.predecible == false) {
                    float rumboColectivo, rumboParada;
                    try {
                        rumboColectivo = this.obtenerRumbo();
                        Parada paradaCercana;
                        if(this.paradaActual == null) //si no hay una parada asociada al colectivo
                            //paradaCercana = this.recorrido.encontrarParadaCercana(this.datoActual.getPosicion(), 0);
                            paradaCercana = this.recorrido.encontrarParadaCercana(nuevoDato.getPosicion(), 0);
                        else
                            //paradaCercana = this.recorrido.encontrarParadaCercana(this.datoActual.getPosicion(), this.recorrido.obtenerOrden(this.paradaActual));
                            paradaCercana = this.recorrido.encontrarParadaCercana(nuevoDato.getPosicion(),
                                    this.recorrido.obtenerOrden(this.paradaActual));

                        while(paradaCercana != null) {
                            rumboParada = this.recorrido.obtenerRumbo(paradaCercana);
                            if(Math.abs(rumboParada-rumboColectivo) < toleranciaDiferenciaRumbo) {
                                this.paradaPrevia = paradaActual;
                                this.paradaActual = paradaCercana;
                                this.datoPrevio = this.datoActual;
                                this.datoActual = nuevoDato;
                                this.predecible = true;
                                this.penalidades = 0;
                                //actualizarHoraArribo();
                                break;
                            }
                            else
                                paradaCercana = this.recorrido.encontrarParadaCercana(nuevoDato.getPosicion(), this.recorrido.obtenerOrden(paradaCercana));
                        }
                    }
                    catch (datosInsuficientes ex)
                    {

                    }
                }
                else {//si el colectivo es predecible
                    Parada paradaCercana = this.recorrido.encontrarParadaCercana(nuevoDato.getPosicion(), this.recorrido.obtenerOrden(this.paradaActual));
                    if(paradaCercana == null) {
                        //Si el colectivo está a menos de 10 paradas de terminar el recorrido,
                        //debo empezar a buscar desde el pricipio del recorrido por posibles paradas cercanas
                        float disttemp = recorrido.getCantidadParadas() - recorrido.obtenerOrden(paradaActual);
                        if(disttemp < 20)
                            paradaCercana = this.recorrido.encontrarParadaCercana(nuevoDato.getPosicion(), 0);
                    }

                    if(paradaCercana != null) {
                        if(this.recorrido.distancia(paradaActual, paradaCercana)<1600) {
                            this.paradaPrevia = paradaActual;
                            this.paradaActual = paradaCercana;
                            this.datoPrevio = this.datoActual;
                            this.datoActual = nuevoDato;
                            this.predecible = true;
                            this.penalidades = 0;

                            //this.actualizarHoraArribo();
                        }
                        else {// la parada asociada no se acepta porque esta muy lejos
                            penalidades++;
                            if(penalidades >= 3)
                                this.limpiarDatos();
                        }
                    }
                    else {//si no encuentra ninguna parada cercana
                        penalidades++;
                        if(penalidades >= 3)
                            this.limpiarDatos();
                    }
                }
            }
            else {// si la distancia del nuevo dato y el actual es muy poca
                if(datoActual != null &&
                        this.paradaActual.calcularDistancia(nuevoDato.getPosicion()) <
                        this.paradaActual.calcularDistancia(datoActual.getPosicion()))
                    this.datoActual = nuevoDato; // solamente actualizo el datoActual

                ultimosDatos.add(nuevoDato);
            }

            //si paradaActual cambió
            if(paradaActual!=null && paradaPrevia!=null && paradaActual != paradaPrevia
                    && paradaActualTemp != paradaActual.getId()) {
                if(paradaActual.calcularDistancia(datoActual.getPosicion()) <30
                        && paradaPrevia.calcularDistancia(datoPrevio.getPosicion()) < 30) {
                    ArrayList<DatoProcesado> dp = this.interpolarDatos();
                    return dp;
                }
            }
        }
        //si con el nuevo dato recibido no hay cambios en la paradaActual y paradaPrevia, no interpolo datos
        return null;
    }

    /*
     * Devuelve el rumbo del colectivo en grados basado en los últimos dos datos
     */
    float obtenerRumbo() throws datosInsuficientes {
        //if(this.datoPrevio != null && this.datoActual != null)
        if(ultimosDatos.size()>1) {
//            LatLonPoint principio = datoPrevio.getPosicion();
//            LatLonPoint fin = datoActual.getPosicion();
            LatLonPoint principio = ultimosDatos.get(ultimosDatos.size()-2).getPosicion();
            LatLonPoint fin = ultimosDatos.get(ultimosDatos.size()-1).getPosicion();
            //TODO codigo repetido con la el metodo obtener rumbo de la clase recorrido
            float rumbo = (float) (principio.azimuth(fin) * 180 / Math.PI);

            if(rumbo < 0)
                rumbo = rumbo + 360;
            return rumbo;
        }
        else
            throw new datosInsuficientes("No se poseen datos suficientes para calcular el rumbo del colectivo");
    }

    public Parada obtenerParadaActual(){
        return this.paradaActual;
    }

    public ArrayList<DatoProcesado> interpolarDatos() {
        if(paradaActual != null && paradaPrevia != null) {
//            if(this.recorrido.obtenerParadaSiguiente(paradaPrevia.getId()) != paradaActual)
                ArrayList<DatoProcesado> datosProcesados = new ArrayList<DatoProcesado>(0);

                long diferenciaEnSeg = (this.datoActual.getFecha().getTimeInMillis() -
                                    this.datoPrevio.getFecha().getTimeInMillis())/1000;

//                long diferenciaEnSeg = (this.horaArriboParadaActual.getTimeInMillis() -
//                                    this.horaArriboParadaPrevia.getTimeInMillis())/1000;
                float distanciaTotal = 0;
                ArrayList<Float> distancias = new ArrayList<Float>(0);
                Parada paradaSiguiente;
                Parada paradaAnterior = paradaPrevia;
                do {
                    paradaSiguiente = this.recorrido.obtenerParadaSiguiente(paradaAnterior.getId());
                    float dist = new Float(paradaAnterior.calcularDistancia(paradaSiguiente.getPosicion()));
                    distancias.add(dist);
                    distanciaTotal += dist;
                    datosProcesados.add(new DatoProcesado(paradaAnterior,null));
                    paradaAnterior = paradaSiguiente;
                } while(paradaSiguiente != paradaActual);

                int anio, mes, dia, hora, min, seg;
                anio = this.datoPrevio.getFecha().get(GregorianCalendar.YEAR);
                mes = this.datoPrevio.getFecha().get(GregorianCalendar.MONTH);
                dia = this.datoPrevio.getFecha().get(GregorianCalendar.DATE);
                hora = this.datoPrevio.getFecha().get(GregorianCalendar.HOUR_OF_DAY);
                min = this.datoPrevio.getFecha().get(GregorianCalendar.MINUTE);
                seg = this.datoPrevio.getFecha().get(GregorianCalendar.SECOND);

//                anio = this.horaArriboParadaPrevia.get(GregorianCalendar.YEAR);
//                mes = this.horaArriboParadaPrevia.get(GregorianCalendar.MONTH);
//                dia = this.horaArriboParadaPrevia.get(GregorianCalendar.DATE);
//                hora = this.horaArriboParadaPrevia.get(GregorianCalendar.HOUR_OF_DAY);
//                min = this.horaArriboParadaPrevia.get(GregorianCalendar.MINUTE);
//                seg = this.horaArriboParadaPrevia.get(GregorianCalendar.SECOND);

                float dist = 0;
                int tiempoPrevio =0;
                for(int i = 0; i < distancias.size(); i++) {
                    dist += distancias.get(i);
                    GregorianCalendar fecha = new GregorianCalendar(anio,mes,dia,hora,min,seg);
                    int proporcionTiempo = (int) (diferenciaEnSeg * dist/distanciaTotal);

                    datosProcesados.get(i).setHoraArribo(fecha);
                    fecha.add(GregorianCalendar.SECOND, proporcionTiempo);
                                        
                    datosProcesados.get(i).setTiempoSegundos(proporcionTiempo-tiempoPrevio);
                    tiempoPrevio = proporcionTiempo;
                    datosProcesados.get(i).setIdcolectivo(idColectivo);
                }
                return datosProcesados;
            }
        else
            return null;
    }

    private void actualizarHoraArribo() {
        boolean llegando; //me indica si el colectivo esta llegando o partiendo de la parada
        llegando = estaArribando();
        this.horaArriboParadaPrevia.setTimeInMillis(this.horaArriboParadaActual.getTimeInMillis());

        double distancia = this.paradaActual.calcularDistancia(this.datoActual.getPosicion());
        if(distancia > 60) {
            if(llegando == true) {
                int segundos = (int) (distancia / velocidadPromedio());
                this.horaArriboParadaActual.setTimeInMillis(this.datoActual.getFecha().getTimeInMillis()
                        +(long)(segundos*1000*0.5));
    //            this.horaArriboParadaActual.setTimeInMillis(this.datoActual.getFecha().getTimeInMillis());
            }
            else {
                int segundos = (int) (distancia / velocidadPromedio());
                this.horaArriboParadaActual.setTimeInMillis(this.datoActual.getFecha().getTimeInMillis()
                        -(long)(segundos*1000*0.5));
    //            this.horaArriboParadaActual.setTimeInMillis(this.datoActual.getFecha().getTimeInMillis());
            }
        }
        else
            this.horaArriboParadaActual.setTimeInMillis(this.datoActual.getFecha().getTimeInMillis());
    }

    private boolean estaArribando() {
        LatLonPoint aux;
        aux = this.paradaActual.getPosicion().getPoint(com.bbn.openmap.proj.Length.METER.toRadians(10)
                , paradaActual.getPosicion().azimuth(this.recorrido.obtenerParadaSiguiente(paradaActual.getId()).getPosicion()));

        double distanciaParada = datoActual.calcularDistancia(paradaActual.getPosicion());
        double distanciaAux = datoActual.calcularDistancia(aux);

        if(distanciaParada < distanciaAux)
            return true;
        else
            return false;
    }

    private float velocidadPromedio() {
        float distancia = 0;
        int tiempo = 0;

        if(this.ultimosDatos.size()>=4) {
            for(int i = ultimosDatos.size()-1; i > ultimosDatos.size()-4; i--) {
                distancia += ultimosDatos.get(i).calcularDistancia(ultimosDatos.get(i-1).getPosicion());
                tiempo += ultimosDatos.get(i).getFecha().getTimeInMillis() - ultimosDatos.get(i-1).getFecha().getTimeInMillis();
            }
        }
        return distancia/tiempo*1000;
    }
}
